package cn.felord.security.autoconfigure;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.ProviderNotFoundException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author felord.cn
 * @since 1.0.8.RELEASE
 */
public class ChannelAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    private static final String CHANNEL_URI_VARIABLE_NAME = "channel";
    private static final RequestMatcher LOGIN_REQUEST_MATCHER = new AntPathRequestMatcher("/login/{" + CHANNEL_URI_VARIABLE_NAME + "}", "POST");

    private final List<LoginChannelProcessor<? extends AbstractAuthenticationToken>> channelFilters;

    public ChannelAuthenticationFilter(List<LoginChannelProcessor<? extends AbstractAuthenticationToken>> channelFilters) {
        super(LOGIN_REQUEST_MATCHER, authentication -> null);
        this.channelFilters = CollectionUtils.isEmpty(channelFilters) ? Collections.emptyList() : channelFilters;
        List<AuthenticationProvider> providers = this.channelFilters.stream()
                .map(LoginChannelProcessor::getProvider)
                .collect(Collectors.toList());
        ProviderManager authenticationManager = new ProviderManager(providers);
        setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException {
        String channel = LOGIN_REQUEST_MATCHER.matcher(request)
                .getVariables()
                .get(CHANNEL_URI_VARIABLE_NAME);
        for (LoginChannelProcessor<? extends AbstractAuthenticationToken> loginChannelProcessor : channelFilters) {
            if (loginChannelProcessor.supports(channel)) {
                return this.getAuthenticationManager().authenticate(loginChannelProcessor.authenticationRequest(request));
            }
        }
        throw new ProviderNotFoundException("No Suitable Provider");
    }

}
